package com.lipaluma.test.db.internal.assertions;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.datasource.DataSourceUtils;

import com.lipaluma.test.db.annotations.assertions.AssertColumns;
import com.lipaluma.test.db.annotations.assertions.AssertDefinedColumns;
import com.lipaluma.test.db.exceptions.DBTestIllegalUseException;
import com.lipaluma.test.db.exceptions.SQLRequestFailedException;
import com.lipaluma.test.db.internal.DbUtils;
import com.lipaluma.test.db.internal.assertions.Column.Builder;

/**
 * Utility Class used for : 
 * {@link AssertColumns}, {@link AssertDefinedColumns}
 * @author Mario
 *
 */
public class AssertColumnUtils {

	private static Map<String, String> params;
	
	public static void doAssertion(AssertColumns assertColumn) throws IOException, SQLException {
		if(assertColumn != null) {
			String request = assertColumn.request();
			if(StringUtils.isBlank(request))
				throw new DBTestIllegalUseException("the request can't be null or empty");
			
			DataSource ds = getDataSource(assertColumn.dataSource());
			Connection connection = DataSourceUtils.getConnection(ds);
			ResultSet rs = executeRequest(connection, request);
			
			ColumnType defaultType = null;
			if(assertColumn.type().length == 1)
				defaultType = assertColumn.type()[0];
			else if(assertColumn.type().length != assertColumn.expected().length)
				throw new DBTestIllegalUseException("In case of type is defined with more than one value, the count must matches expected values count.");
				
			String[] expectedValues = assertColumn.expected();
			for(int i=0; i < expectedValues.length; i++) {
				ColumnType type = defaultType;
				if(type == null)
					type = assertColumn.type()[i];
				Builder<?> columnBuilder = type.createColumnBuilder(request).withNullValue(assertColumn.nullValue()).onIndex(i+1);
				String expectedValue = params.get(expectedValues[i]) != null ? params.get(expectedValues[i]) : expectedValues[i];
				Column<?> column = columnBuilder.withExpectedValue(expectedValue).build();
				column.verifyExpectedValueFromResultSet(rs);
			}
		}
	}

	public static void doAssertion(AssertDefinedColumns assertColumns) throws IOException, SQLException {
		if(assertColumns != null) {
			String request = assertColumns.request();
			if(StringUtils.isBlank(request))
				throw new DBTestIllegalUseException("the request can't be null or empty");
			
			DataSource ds = DbUtils.getDataSource();
			Connection connection = DataSourceUtils.getConnection(ds);
			ResultSet rs = executeRequest(connection, request);
			
			com.lipaluma.test.db.annotations.assertions.Column[] columns = assertColumns.columns();
			for (com.lipaluma.test.db.annotations.assertions.Column column : columns) {
				Builder<?> columnBuilder = column.type().createColumnBuilder(request).withNullValue(column.nullValue()).withName(column.name());
				String expectedValue = params.get(column.expected()) != null ? params.get(column.expected()) : column.expected();
				Column<?> columnAdapted = columnBuilder.withExpectedValue(expectedValue).build();
				columnAdapted.verifyExpectedValueFromResultSet(rs);
			}
			rs.close();
		}
	}

	public static void doAssertion(AssertColumns.List asserts) throws IOException, SQLException {
		if(asserts != null) {
			AssertColumns[] assertColumnsArray = asserts.value();
			for (AssertColumns assertColumns : assertColumnsArray) {
				doAssertion(assertColumns);
			}
		}
	}

	public static void doAssertion(AssertDefinedColumns.List asserts) throws IOException, SQLException {
		if(asserts != null) {
			AssertDefinedColumns[] assertColumnsArray = asserts.value();
			for (AssertDefinedColumns assertColumns : assertColumnsArray) {
				doAssertion(assertColumns);
			}
		}
	}

	private static ResultSet executeRequest(Connection connection, String request) throws SQLException {
		for(String param : params.keySet()) {
			request = request.replaceAll(param, params.get(param));
		}
		PreparedStatement ps = connection.prepareStatement(request);
		try {
			ResultSet rs = ps.executeQuery();
			if(!rs.next())
				throw new AssertionError("On request ["+request+"] : no row found on database");
			return rs;
		} catch (SQLException e) {
			throw new SQLRequestFailedException(request, e);
		}
	}

	/**
	 * reset all keys defined in the assertColumns
	 */
	public static void resetKeys() {
		params = new HashMap<String, String>();
	}

	/**
	 * add a key that will be replaced before launching the request with the value
	 * @param key string key to replace
	 * @param value value to put
	 */
	public static void defineValueForKey(String key, Object value) {
		params.put(key, String.valueOf(value));
	}

	private static DataSource getDataSource(String dataSourceName) throws IOException {
		DataSource ds;
		if(dataSourceName.isEmpty())
			ds = DbUtils.getDataSource();
		else 
			ds = DbUtils.getDataSourceWithName(dataSourceName);
		return ds;
	}

}
